package org.magicsoft.office.excel.impt;

import java.sql.Connection;
import java.util.Map;

import org.magicsoft.office.db.DatabaseType;
import org.magicsoft.office.db.DatabaseUtils;
import org.magicsoft.office.db.SQLType;
import org.magicsoft.office.excel.model.Column;
import org.magicsoft.office.excel.model.DefaultValue;
import org.magicsoft.office.excel.model.Reference;
import org.magicsoft.office.excel.model.Sheet;
import org.magicsoft.office.excel.model.TargetTable;
import org.magicsoft.office.exception.UnsupportedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class TempTableSQLBuilder {

	private static final Logger logger = LoggerFactory.getLogger(TempTableSQLBuilder.class);

	public String build(Sheet sheet, Connection conn, DatabaseType databaseType, boolean useTemporaryTable) {
		StringBuilder createBuilder = new StringBuilder();
		StringBuilder insertBuilder = new StringBuilder();
		StringBuilder valuesBuilder = new StringBuilder();

		// 针对模型中声明的列构造SQL语句。
		switch (databaseType) {
		case SQLSERVER:
			createBuilder.append("create table ");
			break;
		case ORACLE:
			this.appendTempTableForOracle(createBuilder, useTemporaryTable);
			break;
		default:
			throw new UnsupportedException();
		}
		createBuilder.append(sheet.getTempTableName());
		createBuilder.append("(");
		insertBuilder.append("insert into ");
		insertBuilder.append(sheet.getTempTableName());
		insertBuilder.append("(");
		for (Map.Entry<String, Column> entry : sheet.getColumns().entrySet()) {
			this.appendColumnDeclare(createBuilder, entry.getValue().getTempTableColumn(),
					entry.getValue().getSqlType(), entry.getValue().getLength(), entry.getValue().getPrecision(),
					databaseType);
			insertBuilder.append(entry.getValue().getTempTableColumn());
			insertBuilder.append(", ");
			valuesBuilder.append("?, ");

			if (entry.getValue().getReference() != null) {
				entry.getValue().getReference().setTempTableColumn(entry.getValue().getTempTableColumn() + "id_");
			}
		}

		this.appendAssistantColumns(sheet, createBuilder, valuesBuilder, insertBuilder, databaseType);

		createBuilder.deleteCharAt(createBuilder.length() - 2); // -2因为包含一个空格，包含空格是为了超长时在表格中方便换行。
		createBuilder.append(")");
		insertBuilder.deleteCharAt(insertBuilder.length() - 2);
		valuesBuilder.deleteCharAt(valuesBuilder.length() - 2);
		insertBuilder.append(") values (");
		insertBuilder.append(valuesBuilder);
		insertBuilder.append(")");
		if (DatabaseType.ORACLE.equals(databaseType) && useTemporaryTable) {
			createBuilder.append(" on commit delete rows");
		}

		// 创建临时表。
		String createSql = createBuilder.toString();
		logger.debug("create sql:" + createSql);
		DatabaseUtils.executeUpdate(conn, createSql);

		// 构建插入语句。
		String currentInsertSQL = insertBuilder.toString();
		logger.debug("insert sql:" + currentInsertSQL);
		return currentInsertSQL;
	}

	/**
	 * 添加一些辅助字段。
	 * 
	 * @param sheet
	 *            sheet模型。
	 * @param createBuilder
	 *            create脚本的StringBuilder。
	 * @param valuesBuilder
	 *            values脚本的StringBuilder。
	 * @param insertBuilder
	 *            insert脚本的StringBuilder。
	 * @param databaseType
	 *            数据库类型。
	 */
	private void appendAssistantColumns(Sheet sheet, StringBuilder createBuilder, StringBuilder valuesBuilder,
			StringBuilder insertBuilder, DatabaseType databaseType) {
		int parameterIndex = sheet.getLastColumn().getParameterIndex();
		for (Reference refColumn : sheet.getReferences()) {
			refColumn.setParameterIndex(++parameterIndex);
			this.appendColumnDeclare(createBuilder, refColumn.getTempTableColumn(),
					refColumn.getRefPrimaryKey().getSqlType(), 255, 2, databaseType);
			insertBuilder.append(refColumn.getTempTableColumn());
			insertBuilder.append(", ");
			valuesBuilder.append("?, ");
		}

		for (TargetTable targetTable : sheet.getTargetTables()) {
			if (targetTable.getDefaultValues() != null) {
				for (DefaultValue defaultValue : targetTable.getDefaultValues()) {
					this.appendColumnDeclare(createBuilder, defaultValue.getColumn(), defaultValue.getSqlType(), 255, 2,
							databaseType);
					defaultValue.setParameterIndex(++parameterIndex);
					insertBuilder.append(defaultValue.getColumn());
					insertBuilder.append(", ");
					valuesBuilder.append("?, ");
				}
			}

			if (targetTable.getPrimaryKey() != null) {
				this.appendColumnDeclare(createBuilder, targetTable.getAlias() + "_oid_", SQLType.VARCHAR, 255, 2,
						databaseType);
				targetTable.setOidParameterIndex(++parameterIndex);
				insertBuilder.append(targetTable.getOidColumn());
				insertBuilder.append(", ");
				valuesBuilder.append("?, ");

				this.appendColumnDeclare(createBuilder, targetTable.getAlias() + "_nid_", SQLType.VARCHAR, 255, 2,
						databaseType);
				targetTable.setNidParameterIndex(++parameterIndex);
				insertBuilder.append(targetTable.getNidColumn());
				insertBuilder.append(", ");
				valuesBuilder.append("?, ");
			}
		}

		this.appendColumnDeclare(createBuilder, "excelrow_", SQLType.INTEGER, 0, 0, databaseType);
		sheet.setExcelRowParameterIndex(++parameterIndex);
		insertBuilder.append("excelrow_, ");
		valuesBuilder.append("?, ");

		this.appendColumnDeclare(createBuilder, "errormsg_", SQLType.CLOB, 0, 0, databaseType);
		sheet.setErrorMsgParameterIndex(++parameterIndex);
		insertBuilder.append("errormsg_, ");
		valuesBuilder.append("?, ");
	}

	private void appendTempTableForOracle(StringBuilder createBuilder, boolean useTemporaryTable) {
		if (useTemporaryTable) {
			createBuilder.append("create global temporary table ");
		} else {
			createBuilder.append("create table ");
		}
	}

	/**
	 * 添加创建临时表脚本中的字段声明部分。
	 * 
	 * @param createBuilder
	 *            建表用StringBuilder。
	 * @param property
	 *            用于生成字段名称的属性。
	 * @param databaseType
	 *            数据库类型。
	 */
	private void appendColumnDeclare(StringBuilder createBuilder, String columnName, SQLType sqlType, int length,
			int precision, DatabaseType databaseType) {
		// 构造结构脚本。
		createBuilder.append(columnName);
		createBuilder.append(" ");
		createBuilder.append(sqlType.getDatabaseType(databaseType));
		if (length != -1 && this.canAppendLength(sqlType, databaseType)) {
			createBuilder.append("(");
			createBuilder.append(this.getLength(length, sqlType, databaseType));
			if (precision != -1 && this.canAppendPrecision(sqlType)) {
				createBuilder.append(", ");
				createBuilder.append(precision);
			}
			createBuilder.append(")");
		}
		if (DatabaseType.SQLSERVER.equals(databaseType)) {
			this.appendCharsetForSQLServer(sqlType, createBuilder);
		}
		createBuilder.append(", ");
	}

	/**
	 * 获取长度，一般情况下，长度就取自列的长度，但是对于Oracle的数字而言，最大为38。
	 * 
	 * @param length
	 *            列模型中的长度。
	 * @param sqlType
	 *            字段类型。
	 * @param databaseType
	 *            数据库类型。
	 * @return 最终的长度值。
	 */
	private int getLength(int length, SQLType sqlType, DatabaseType databaseType) {
		if (this.isOracleNumber(sqlType, databaseType)) {
			return 38;
		}
		return length;
	}

	/**
	 * 是否是Oracle数据库，并且是数字字段。
	 * 
	 * @param sqlType
	 *            字段类型。
	 * @param databaseType
	 *            数据库类型。
	 * @return 是否是Oracle数据库，并且是数字字段。
	 */
	private boolean isOracleNumber(SQLType sqlType, DatabaseType databaseType) {
		if (DatabaseType.ORACLE.equals(databaseType)) {
			switch (sqlType) {
			case BOOLEAN:
			case BIT:
			case INTEGER:
			case BIGINT:
			case DECIMAL:
			case DOUBLE:
			case FLOAT:
			case REAL:
				return true;
			default:
				return false;
			}
		}
		return false;
	}

	/**
	 * 针对SQLServer数据库，临时表的字符集要和数据库一致。
	 * 
	 * @param sqlType
	 *            字段类型。
	 * @param createBuilder
	 *            建库脚本。
	 */
	private void appendCharsetForSQLServer(SQLType sqlType, StringBuilder createBuilder) {
		switch (sqlType) {
		case CHAR:
		case LONGNVARCHAR:
		case LONGVARCHAR:
		case NCHAR:
		case NVARCHAR:
		case VARCHAR:
			createBuilder.append(" collate database_default");
			break;
		default:
			break;
		}
	}

	/**
	 * 是否允许声明长度。
	 * 
	 * @param sqlType
	 *            字段类型。
	 * @param databaseType
	 *            数据库类型。
	 * @return 是否允许声明长度。
	 */
	private boolean canAppendLength(SQLType sqlType, DatabaseType databaseType) {
		switch (sqlType) {
		case CHAR:
		case NCHAR:
		case VARCHAR:
		case NVARCHAR:
			return true;
		default:
			break;
		}
		if (this.isOracleNumber(sqlType, databaseType)) {
			return true;
		}
		return false;
	}

	/**
	 * 是否允许声明精度。
	 * 
	 * @param sqlType
	 *            字段类型。
	 * @return 是否允许声明精度。
	 */
	private boolean canAppendPrecision(SQLType sqlType) {
		switch (sqlType) {
		case DOUBLE:
		case FLOAT:
		case REAL:
			return true;
		default:
			break;
		}
		return false;
	}

}
